package org.bigk.invoices.services;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bigk.invoices.Globals;
import org.bigk.invoices.exceptions.ServiceException;
import org.bigk.invoices.model.Invoice;
import org.bigk.invoices.model.InvoiceFilter;
import org.bigk.invoices.model.InvoicePosition;
import org.bigk.invoices.model.InvoicePositionSummary;
import org.bigk.invoices.model.PaymentKind;
import org.bigk.invoices.model.Tax;
import org.bigk.invoices.utils.HibernateUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import pl.bzwbk.zriab.utils.lexengines.LexEngine;
import pl.bzwbk.zriab.utils.lexengines.LexEnginePL;

public class InvoicesServiceImpl implements InvoicesService {
	/**
	 * Logger for this class
	 */
	private static final Log logger =
		LogFactory.getLog(InvoicesServiceImpl.class);

	protected PaymentKindsService paymentKindsService;
	protected TaxesService taxesService;
	protected HRInvoiceNumberService hrInvoiceNumberService;
	protected Invoice invoiceTemplate;

	public InvoicesServiceImpl() {
	}

	@SuppressWarnings("unchecked")
	public List<Invoice> listAllItems() throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("listAllItems() - start");
		}

		List<Invoice> list = null;
		Session session = null;
		try {
			session = HibernateUtils.getCurrentSession();
			
			Criteria crit = session.createCriteria(Invoice.class);
			crit.addOrder(Order.desc("year"));
			crit.addOrder(Order.desc("number"));
			
			list = crit.list();
			
			if (list != null) {
				for (Invoice invoice : list) {
					recalculateInvoice(invoice);
					hrInvoiceNumberService.updateHRInvoiceNumber(invoice);
				}
			}
			
		} catch (HibernateException ex) {
			logger.error("listAllItems()", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("listAllItems() - end - return value=" + CollectionUtils.size(list));
		}
		return list;
	}

	@SuppressWarnings("unchecked")
	public List<Invoice> listItems4Page(InvoiceFilter filter) throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("listItems4Page(InvoiceFilter filter=" + filter + ") - start");
		}

		List<Invoice> list = null;
		Session session = null;
		try {
			session = HibernateUtils.getCurrentSession();
			
			Criteria crit = session.createCriteria(Invoice.class);
			crit.addOrder(Order.desc("year"));
			crit.addOrder(Order.desc("number"));
			
			if (filter != null) {
				if (filter.getId() != null)
					crit.add(Restrictions.idEq(filter.getId()));
				if (StringUtils.isNotEmpty(filter.getYear()))
					crit.add(Restrictions.eq("year", filter.getYear()));
				if (filter.getNumber() != null)
					crit.add(Restrictions.eq("number", filter.getNumber()));
				if (StringUtils.isNotEmpty(filter.getPurchaserName()))
					crit.add(Restrictions.like("purchaserName", filter.getPurchaserName(), MatchMode.ANYWHERE));
			}
			HibernateUtils.updateStats(crit, filter);
			int firstResult = (filter.getCurrentPage().intValue() - 1)
					* filter.getPaginatedPageSize().intValue();
			int maxResults = filter.getPaginatedPageSize().intValue();
			crit.setFirstResult(firstResult);
			crit.setMaxResults(maxResults);
			
			list = crit.list();
			
			if (list != null) {
				for (Invoice invoice : list) {
					recalculateInvoice(invoice);
					hrInvoiceNumberService.updateHRInvoiceNumber(invoice);
				}
			}

		} catch (HibernateException ex) {
			logger.error("listItems4Page(InvoiceFilter)", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("listItems4Page(InvoiceFilter) - end - return value=" + CollectionUtils.size(list));
		}
		return list;
	}

	public Invoice getInvoice(Long id) throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("getInvoice(Long id=" + id + ") - start");
		}

		Invoice object = null;
		Session session = null;
		try {
			session = HibernateUtils.getCurrentSession();
			object = (Invoice) session.get(Invoice.class, id);
			
			recalculateInvoice(object);
			hrInvoiceNumberService.updateHRInvoiceNumber(object);
			
		} catch (HibernateException ex) {
			logger.error("getInvoice(Long)", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("getInvoice(Long) - end - return value=" + object);
		}
		return object;
	}

	public Invoice prepareNewInvoice() throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("prepareNewInvoice() - start");
		}
		
		Invoice invoice = null;
		if (this.invoiceTemplate != null) {
			try {
				invoice = invoiceTemplate.clone();
			} catch (CloneNotSupportedException e) {
				logger.warn("prepareNewInvoice()", e);
			}
		}
		
		if (invoice == null) {
			invoice = new Invoice();
		}
		
		Calendar nowDay = Calendar.getInstance();
		nowDay.set(Calendar.HOUR, 0);
		nowDay.set(Calendar.MINUTE, 0);
		nowDay.set(Calendar.SECOND, 0);
		nowDay.set(Calendar.MILLISECOND, 0);
		
		Long curYear = new Long(nowDay.get(Calendar.YEAR));
		invoice.setYear(curYear.toString());
		
		int maxInvoiceNumber = this.getMaxInvoiceNumber(curYear.toString());
		maxInvoiceNumber++;
		
		Long number = new Long(maxInvoiceNumber);
		invoice.setNumber(number);
		invoice.setDocumentDate(nowDay.getTime());
		invoice.setSoldDate(nowDay.getTime());
		
		PaymentKind pk = null;
		if (invoice.getPaymentKindId() != null) {
			pk = paymentKindsService.getPaymentKind(invoice.getPaymentKindId());
		}
		
		Date paymentDate = null;
		if (pk != null) {
			invoice.setPaymentKind(pk);
			paymentDate = paymentKindsService.calculatePaymentDate(nowDay.getTime(), pk.getId());
			invoice.setPaymentDate(paymentDate);
		}
		
		recalculateInvoice(invoice);
		hrInvoiceNumberService.updateHRInvoiceNumber(invoice);
		
		if (logger.isDebugEnabled()) {
			logger.debug("prepareNewInvoice() - end - return value=" + invoice);
		}
		return invoice;
	}

	protected int getMaxInvoiceNumber(String year) throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("getMaxInvoiceNumber(String year=" + year + ") - start");
		}

		Session session = null;

		int maxNumber = 0;
		try {
			session = HibernateUtils.getCurrentSession();
			Criteria crit = session.createCriteria(Invoice.class);
			crit.add(Restrictions.eq("year", year));
			crit.setProjection(Projections.max("number"));
			Long maxNumberLong = (Long) crit.uniqueResult();
			if (maxNumberLong != null)
				maxNumber = maxNumberLong.intValue();
			crit.setProjection(null);
			crit.setResultTransformer(Criteria.ROOT_ENTITY);
		} catch (HibernateException ex) {
			logger.error("getMaxInvoiceNumber(Long)", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("getMaxInvoiceNumber(String) - end - return value=" + maxNumber);
		}
		return maxNumber;
	}

	public void saveInvoice(Invoice invoice) throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("saveInvoice(Invoice invoice=" + invoice + ") - start");
		}

		List<InvoicePosition> positions = invoice.getInvoicePositions();
		if (positions != null) {
			for (InvoicePosition ip : positions) {
				ip.setInvoice(invoice);
			}
		}
		
		Session session = null;
		try {
			session = HibernateUtils.getCurrentSession();
			session.save(invoice);
		} catch (HibernateException ex) {
			logger.error("saveInvoice(Invoice)", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("saveInvoice(Invoice) - end");
		}
	}

	@Override
	public void updateInvoice(Invoice invoice) throws ServiceException {
	}
	
	@Override
	public void addInvoicePosition(Invoice invoice, InvoicePosition invoicePosition)
			throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("addInvoicePosition(Invoice invoice=" + invoice
					+ ", InvoicePosition invoicePosition=" + invoicePosition
					+ ") - start");
		}

		// read and set Tax object
		if (invoicePosition.getTaxId() != null) {
			Tax tax = taxesService.getTax(invoicePosition.getTaxId());
			invoicePosition.setTax(tax);
		}
		
		// set parent of this position
		invoicePosition.setInvoice(invoice);
		
		// add positions to parent's list
		List<InvoicePosition> invoicePositions = invoice.getInvoicePositions();
		if (invoicePositions == null) {
			invoicePositions = new ArrayList<InvoicePosition>();
			invoice.setInvoicePositions(invoicePositions);
		}
		invoicePositions.add(invoicePosition);

		this.recalculateInvoice(invoice);
		hrInvoiceNumberService.updateHRInvoiceNumber(invoice);
		
		if (logger.isDebugEnabled()) {
			logger.debug("addInvoicePosition(Invoice, InvoicePosition) - end");
		}
	}
	
	protected void recalculateInvoice(Invoice invoice) {
		if (logger.isDebugEnabled()) {
			logger.debug("recalculateInvoice(Invoice invoice=" + invoice + ") - start");
		}

		if (invoice == null)
			return;
		
		// create List with keys of summaries
		List<Double> calculatedSummariesKeys = new ArrayList<Double>(4);
		calculatedSummariesKeys.add(Globals.DOUBLE_022);
		calculatedSummariesKeys.add(Globals.DOUBLE_007);
		calculatedSummariesKeys.add(Globals.DOUBLE_003);
		calculatedSummariesKeys.add(Globals.DOUBLE_0);
		
		invoice.setCalculatedSummariesKeys(calculatedSummariesKeys);
		
		
		// create Map with summaries
		Map<Double, InvoicePositionSummary> calculatedSummaries =
			new HashMap<Double, InvoicePositionSummary>(5);
		
		calculatedSummaries.put(Globals.DOUBLE_022,
				new InvoicePositionSummary(Globals.DOUBLE_022));
		calculatedSummaries.put(Globals.DOUBLE_007,
				new InvoicePositionSummary(Globals.DOUBLE_007));
		calculatedSummaries.put(Globals.DOUBLE_003,
				new InvoicePositionSummary(Globals.DOUBLE_003));
		calculatedSummaries.put(Globals.DOUBLE_0,
				new InvoicePositionSummary(Globals.DOUBLE_0));
		
		// position with 'total' aggregates
		InvoicePositionSummary ip1 = new InvoicePositionSummary(Globals.DOUBLE_1);
		calculatedSummaries.put(Globals.DOUBLE_1, ip1);
		
		invoice.setCalculatedSummaries(calculatedSummaries);
		
		
		if (invoice.getInvoicePositions() != null) {
			for (InvoicePosition pos : invoice.getInvoicePositions()) {
				if (logger.isDebugEnabled()) {
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - pos=" + pos);
				}
				
				Double percentTax = pos.getTax().getValue();
				if (logger.isDebugEnabled()) {
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - percentTax=" + percentTax);
				}

				InvoicePositionSummary posFromMap =
					(InvoicePositionSummary) calculatedSummaries.get(percentTax);
				if (logger.isDebugEnabled()) {
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - before add - posFromMap=" + posFromMap);
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - before add  - ip1=" + ip1);
				}
				
				if (posFromMap != null) {
					posFromMap.addValueNetto(pos.getValueNetto());
					posFromMap.addSum(pos.getSum());
					posFromMap.addTotal(pos.getTotal());
				}
				
				ip1.addValueNetto(pos.getValueNetto());
				ip1.addSum(pos.getSum());
				ip1.addTotal(pos.getTotal());
				
				if (logger.isDebugEnabled()) {
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - after add - posFromMap=" + posFromMap);
					logger.debug("recalculateInvoice(Invoice) - iterate through invoicePositions - after add  - ip1=" + ip1);
				}
			}
			
			// get text for ip1::total
			BigDecimal total = ip1.getTotal();
			LexEngine le = new LexEnginePL();
			
			long integerPart = total.longValue();
			long digitsPart = total.multiply(new BigDecimal(100d)).remainder(new BigDecimal(100d)).longValue();
			
			invoice.setHrTotalIntegerPart(le.getTextForNumber(integerPart));
			invoice.setHrTotalDigitsPart(le.getTextForNumber(digitsPart));
			
			if (logger.isDebugEnabled()) {
				logger.debug("recalculateInvoice(Invoice) - set - hrTotalIntegerPart=" + invoice.getHrTotalIntegerPart());
				logger.debug("recalculateInvoice(Invoice) - set - hrTotalDigitsPart=" + invoice.getHrTotalDigitsPart());
			}
		}

		if (logger.isDebugEnabled()) {
			logger.debug("recalculateInvoice(Invoice) - end");
		}
	}
	
	public void deleteInvoice(Invoice invoice) throws ServiceException {
		if (logger.isDebugEnabled()) {
			logger.debug("deleteInvoice(Invoice invoice=" + invoice + ") - start");
		}

		Session session = null;
		try {
			session = HibernateUtils.getCurrentSession();
			session.delete(invoice);
		} catch (HibernateException ex) {
			logger.error("deleteInvoice(Invoice)", ex);
			throw new ServiceException(ex);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("deleteInvoice(Invoice) - end");
		}
	}
	
	public PaymentKindsService getPaymentKindsService() {
		return paymentKindsService;
	}

	public void setPaymentKindsService(PaymentKindsService paymentKindsService) {
		this.paymentKindsService = paymentKindsService;
	}

	public TaxesService getTaxesService() {
		return taxesService;
	}

	public void setTaxesService(TaxesService taxesService) {
		this.taxesService = taxesService;
	}

	public HRInvoiceNumberService getHrInvoiceNumberService() {
		return hrInvoiceNumberService;
	}

	public void setHrInvoiceNumberService(
			HRInvoiceNumberService hrInvoiceNumberService) {
		this.hrInvoiceNumberService = hrInvoiceNumberService;
	}

	public Invoice getInvoiceTemplate() {
		return invoiceTemplate;
	}

	public void setInvoiceTemplate(Invoice invoiceTemplate) {
		this.invoiceTemplate = invoiceTemplate;
	}
	
}
